using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Common.Interfaces;
using Common.Gui.GuiWinFormsLib.Helpers;
using System.Diagnostics;

namespace Common.Gui.GuiWinFormsLib.Controls
{
	 public delegate void ContainerStateChangedHandler(object sender, EventArgs e);

	internal partial class DockingContainer : UserControl
	{
		/// <summary>
		/// origin height of the container,
		/// used after minimize/restore
		/// </summary>
		protected int originHeight;
		/// <summary>
		/// origin width of the container,
		/// used after minimize/restore
		/// </summary>
		protected int originWidth;
		
		protected Size preferredMinimumSize;

		protected int controlBarHeight;

		/// <summary>
		/// occurs when whole panel is minimized with all its panels 
		/// </summary>
		public event ContainerStateChangedHandler OnContainerMinimized;
		
		/// <summary>
		/// occurs when minimized panel is restored again 
		/// </summary>
		public event ContainerStateChangedHandler OnContainerRestored;
		
		/// <summary>
		/// occurs when container alignment changed 
		/// </summary>
		public event PanelAlignmentChanged OnAlignmentChanged;

		/// <summary>
		/// occurs when ControlBarHeight property is changed
		/// </summary>
		public event PropertyChangedHandler<int> OnControlBarHeightChanged;

		protected PanelAlignment controlBarAlinment;
  
		/// <summary>
		/// container alignment - determines where is control panel rendered 
		/// </summary>
		public PanelAlignment ContainerAlignment
		{
			get { return controlBarAlinment; }
			set 
			{
				controlBarAlinment = value;
				if (this.OnAlignmentChanged != null)
					this.OnAlignmentChanged(this, new AlignmentEventArgs(controlBarAlinment, this));
			}
		}

		/// <summary>
		/// if set to false, last dockable panel inside won't be able to detach from this container 
		/// </summary>
		public bool EnableEmptyContainer
		{
			get;
			set;
		}

		/// <summary>
		/// Height of control bar, bar contains buttons for panel operations
		/// such as minimize, restore
		/// </summary>
		/// <remarks>
		/// value less then 12 is not recommended
		/// </remarks>
		public int ControlBarHeight
		{
			get { return this.controlBarHeight;  }
			set 
			{
				this.controlBarHeight = value;

				if (this.OnControlBarHeightChanged != null)
					this.OnControlBarHeightChanged(this, new PropertyChangedEventArgs<int>(this.controlBarHeight));
			}
		}

		protected readonly PanelManager<SwitcherButton> manager;

		/// <summary>
		/// default #ctor
		/// </summary>
		public DockingContainer()
		{
			InitializeComponent();
			// this makes troubles if it is in designer part
			//this.ControlBarHeight = 16;

			this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;

			this.Load += new EventHandler(DockingContainer_Load);
			this.ControlAdded += new ControlEventHandler(DockingContainer_ControlAdded);

			//this.Resize += HandleSizeChanged;

			this.OnAlignmentChanged += new PanelAlignmentChanged(DockingContainer_OnAlignmentChanged);

			this.manager = new PanelManager<SwitcherButton>(this);
		}

		void DockingContainer_OnAlignmentChanged(object sender, AlignmentEventArgs ae)
		{
			switch (ae.Alignment)
			{
				case PanelAlignment.VerticalLeft:
					this.controlPanel.Dock = DockStyle.Left;
					break;
				case PanelAlignment.HorizontalBottom:
					this.controlPanel.Dock = DockStyle.Bottom;
					break;
				case PanelAlignment.HorizontalTop:
					this.controlPanel.Dock = DockStyle.Top;
					break;
				case PanelAlignment.VerticalRight:
					this.controlPanel.Dock = DockStyle.Right;
					break;
				case PanelAlignment.None:
					//break;
				default:
					throw new ArgumentException("Unknown panel alignment", "ae");
			}
		}

		protected void HandleSizeChanged (object sender, EventArgs e)
		{
			// not used this time!

			//if (!this.Visible)
			//    return;
			var newHeight = this.Height - this.controlPanel.Height; ///*- this.Padding.Top - this.contentPanel.Padding.Top*/ - 4;	//HACK

			// avoid unnccessary resize events
			if (this.contentPanel.Height != newHeight ||
				this.contentPanel.Width != this.Width)
			{
				Size newSize = new Size(this.Width, newHeight);
				this.originHeight = newHeight;
				this.contentPanel.Size = newSize;
			}
		}

		/// <summary>
		/// minimize the container on the given value,
		/// the value is seto for a width or a height according to
		/// the container alignment
		/// </summary>
		/// <param name="minimizedHeight"></param>
		private void MinimizeContainer(int minimizedHeight)
		{
			this.contentPanel.Visible = false;

			// Update size
			if (this.Dock == DockStyle.Right ||
				this.Dock == DockStyle.Left)
			{
				this.Width = this.ControlBarHeight;
			}
			else
				this.Height = this.ControlBarHeight;
		}

		/// <summary>
		/// check if it is possible to minimze the container and if it is,
		/// call Minimize and invoke the container minimized event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void TryContainerMinimize(object sender, PanelChangedEventArgs e)
		{
			bool allMinimized = true;

			allMinimized = this.manager.AreAllPanelsOutside;

			if (allMinimized)
			{
				var minimizedHeight = this.controlPanel.Height;
				if (sender != null && (sender is DockablePanel))
				{
					DockablePanel dockPanel = sender as DockablePanel;
					minimizedHeight = dockPanel.ControlsBarHeight;

					Debug.Assert(dockPanel.CurrentState != DockablePanelState.Normal, "Minimizing container altough it's in normal state.");
				}

				// when we want to minimize, we need to set the minimum size to zero
				// it is set back after restore
				this.MinimumSize = new Size(0, minimizedHeight);

				MinimizeContainer(minimizedHeight);

				Logging.Logger.AddInfo(this.ToString() + " minimized...");

				// fire up the event
				if (this.OnContainerMinimized != null)
					OnContainerMinimized(sender, e);
			}
		}

		/// <summary>
		/// restore container's origin size and fire up the event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void RestoreContainer(object sender, PanelChangedEventArgs e)
		{
			Debug.Assert(!this.preferredMinimumSize.IsEmpty);
			
			if (sender is IDockablePanel)
				this.MinimumSize = this.preferredMinimumSize;
				//this.MinimumSize = (sender as IDockablePanel).PreferredMinimumSize;
			else if (sender == null)
				Logging.Logger.AddWarning("Restore came with null sender");
			else
				Logging.Logger.AddWarning("Restore came with unknown sender: " + sender.ToString());

			this.contentPanel.Visible = true;

			Logging.Logger.AddInfo(this.ToString() + " restored...");

			Debug.Assert((sender as DockablePanel).CurrentState == DockablePanelState.Normal);
			if (this.OnContainerRestored != null)
				OnContainerRestored(sender, e);
		}


		/// <summary>
		/// after load find out the proper height and stored it
		/// it is used later to restore containe
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void DockingContainer_Load(object sender, EventArgs e)
		{
			this.originHeight = this.Height;
			this.originWidth = this.Width;

			// the container should be hidden on the begining
			TryContainerMinimize(sender, PanelChangedEventArgs.EventArgsHidden);
		}

		/// <summary>
		/// check addded controls in container and register the DockablePanel ones
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void DockingContainer_ControlAdded(object sender, ControlEventArgs e)
		{
			try
			{
				//redirect the control to the content panel
				if (!e.Control.Equals(this.controlPanel) ||
					!e.Control.Equals(this.contentPanel))	//do not add main container panels
				{
					this.Controls.Remove(e.Control);
					e.Control.Parent = this.contentPanel;
					this.contentPanel.Controls.Add(e.Control);
				}
				
				 //check if control added is a dockable panel and if so,
				 //register him to make possible to dock it in
				 if (e.Control is DockablePanel)
				 {
					DockablePanel panel = (e.Control as DockablePanel);
					this.RegisterDockablePanel(panel); 
				 }
				 else
				 {
					 Logging.Logger.AddInfo(e.Control.Name + " added to DockingContainer " + this.Name);
				 }
			}
			catch (Exception ex)
			{
				Logging.Logger.AddException(ex)	;
#if DEBUG
				MessageBox.Show("The control wasn't able to register in Docking container.\n " + ex.ToString(), "Exception in control registration",
						MessageBoxButtons.OK, MessageBoxIcon.Warning); 
#else
				MessageBox.Show("The control wasn't able to register in Docking container.", "Exception in control registration",
						MessageBoxButtons.OK, MessageBoxIcon.Warning); 
#endif
			}
		}

		/// <summary>
		/// add dockable panel to list and subscribe to its change event
		/// </summary>
		/// <param name="dockablePanel"></param>
		protected void RegisterDockablePanel(IDockablePanel dockablePanel)
		{
			if (dockablePanel == null)
				throw new ArgumentNullException("dockablePanel", "dockablePanel is null.");

			dockablePanel.HandleDockablePanelStateChanged += new DockablePanelStateChanged(dockablePanel_HandleDockablePanelStateChanged);

			if (dockablePanel.Alignment == PanelAlignment.None)
			{
				if (this.Dock == DockStyle.Bottom || this.Dock == DockStyle.Top || this.Dock == DockStyle.Fill)
				{
					dockablePanel.Alignment = PanelAlignment.HorizontalBottom;
				}
				else if (this.Dock == DockStyle.Left)
				{
					dockablePanel.Alignment = PanelAlignment.VerticalLeft;
				}
				else if (this.Dock == DockStyle.Right)
				{
					dockablePanel.Alignment = PanelAlignment.VerticalRight;
				}
			}

			// TODO - this way, the first added panel wins
			// saving the layout would be fine :)

			Size size = this.MinimumSize;
			if (size.Width < dockablePanel.PreferredMinimumSize.Width)
				size.Width = dockablePanel.PreferredMinimumSize.Width;
			if (size.Height < dockablePanel.PreferredMinimumSize.Height)
				size.Height = dockablePanel.PreferredMinimumSize.Height;
			
			this.MinimumSize = size;
			this.preferredMinimumSize = size;

			// if there is already some panel, hide this one,
			// to make only one panel active at the time
			if (this.manager.PanelCount > 0)
				dockablePanel.CurrentState = DockablePanelState.Hidden;
			else
				dockablePanel.CurrentState = DockablePanelState.Normal;

			//CreatePanelSwitcherButton(dockablePanel);
			// register panel in manager, create switcher button and ad it to the control bar
			this.manager.RegisterPanel(dockablePanel);

			Logging.Logger.AddInfo("Registered dockable panel " + dockablePanel.ToString() + " to " + this.Name);
		}

		/// <summary>
		/// add switcher button to the control bar;
		/// for each dockable panel is one switcher button
		/// </summary>
		/// <param name="switcherButton"></param>
		internal void AddSwitcherButton(ISwitcher switcherButton)
		{
			Debug.Assert(switcherButton != null, "switcherButton is null.");

			this.controlPanel.AddSwitcherButton(switcherButton);
		}
			
		protected void HandleContentPanelControlRemoved (object sender, ControlEventArgs e)
		{
			// TODO removing controls
			Logging.Logger.AddInfo("removing control from the container " + e.Control.Name);
		}
		
		/// <summary>
		/// handle panel state change for each one of the registered dockable panels
		/// </summary>
		/// <param name="sender">must be the DockablePanel which's state was changed</param>
		protected void dockablePanel_HandleDockablePanelStateChanged(object sender)
		{
			if (sender == null || !(sender is DockablePanel))
			{
				Logging.Logger.AddWarning("ContainerMinimized event sent from invalid control! " + sender.ToString());
				return;
			}
			DockablePanel dockPanel = sender as DockablePanel;

			// check if we can change the container state
			switch (dockPanel.CurrentState)
			{
				case DockablePanelState.Hidden:
					TryContainerMinimize(sender, PanelChangedEventArgs.EventArgsHidden);
					break;
				case DockablePanelState.Normal:
					RestoreContainer(sender, PanelChangedEventArgs.EventArgsNormal);
					break;
				case DockablePanelState.Maximalized:
					break;
				case DockablePanelState.Floating:
					TryContainerMinimize(sender, PanelChangedEventArgs.EventArgsFloating);
					break;
				case DockablePanelState.AutoHiding:
					throw new NotImplementedException("Autohide is not implemented yet!");
				default:
					break;
			}
		}
		
		/// <summary>
		/// clear all internal content - remove all dockable panels and dispose them
		/// </summary>
		internal void Clear()
		{
			this.contentPanel.Controls.Clear();
			
			foreach (var panel in this.manager.GetRegisteredPanels())
			{
				if (panel != null && panel is IDisposable)
				{
					(panel as IDisposable).Dispose();
				}
			}
		}

		public override string ToString()
		{
			return string.Format("[Container: {0}, Panels: {1}], Alignment: {2}", this.Name, this.manager.PanelCount, this.ContainerAlignment.ToString());
		}
	}
}
